package com.gitee.huanminabc.utils_tools.redis.redisTemplate.config;


import com.gitee.huanminabc.utils_tools.redis.config.RedisAutoConfiguration;
import lombok.Data;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.*;

@Configuration
@Data
public class RedisTemplateConfig implements InitializingBean {
    public static Map<Integer, RedisTemplate<String, Object>> redisTemplateMap = new HashMap<>();

    @Autowired
    private RedisAutoConfiguration redisAutoConfiguration;
    private static String ADDRESS_PREFIX = "redis://";

    //单机配置
    private RedisConfiguration singleRedisTemplateConfig(RedisAutoConfiguration redisAutoConfiguration) {
        String singleNode = redisAutoConfiguration.getSingleNode();
        RedisAutoConfiguration.NodeRedisConfig nodeConfig = redisAutoConfiguration.getNodeConfig(singleNode);
        //redis配置
        RedisStandaloneConfiguration redisConfiguration = new
                RedisStandaloneConfiguration(nodeConfig.getHost(), nodeConfig.getPort());

        redisConfiguration.setDatabase(redisAutoConfiguration.getDbIndex());
        if (Strings.isNotBlank(nodeConfig.getPassword())) {
            redisConfiguration.setPassword(nodeConfig.getPassword());
        }
        return redisConfiguration;
    }

    //哨兵配置
    private RedisConfiguration sentinelRedisTemplateConfig(RedisAutoConfiguration redisAutoConfiguration) {


        RedisSentinelConfiguration redisConfiguration = new RedisSentinelConfiguration();
        redisConfiguration.setDatabase(redisAutoConfiguration.getDbIndex());
        redisConfiguration.setMaster(redisAutoConfiguration.getSentinelMaster());


        List<Map<String, RedisAutoConfiguration.NodeRedisConfig>> nodes = redisAutoConfiguration.getNodes();
        String password = ""; //哨兵模式中redis的密码是一样的
        Set<RedisNode> sentinels = new HashSet<>();
        for (Map<String, RedisAutoConfiguration.NodeRedisConfig> node : nodes) {
            for (Map.Entry<String, RedisAutoConfiguration.NodeRedisConfig> entry : node.entrySet()) {
                RedisAutoConfiguration.NodeRedisConfig nodeConfig = entry.getValue();
                sentinels.add(new RedisNode(nodeConfig.getHost(), nodeConfig.getPort()));
                if ("".equals(password)) {
                    password = nodeConfig.getPassword();
                } else {
                    if (!password.equals(nodeConfig.getPassword())) {
                        throw new RuntimeException("哨兵模式中redis的密码不一致");
                    }
                }

            }
        }
        if (Strings.isNotBlank(password)) {
            redisConfiguration.setPassword(password);
        }

        // 添加redis节点
        redisConfiguration.setSentinels(sentinels);
        return redisConfiguration;
    }

    //集群配置(不支持切换数据库)
    private RedisConfiguration clusterRedisTemplateConfig(RedisAutoConfiguration redisAutoConfiguration) {
        RedisClusterConfiguration redisConfiguration = new RedisClusterConfiguration();
        List<Map<String, RedisAutoConfiguration.NodeRedisConfig>> nodes = redisAutoConfiguration.getNodes();
        String password = ""; //哨兵模式中redis的密码是一样的
        Set<RedisNode> sentinels = new HashSet<>();
        for (Map<String, RedisAutoConfiguration.NodeRedisConfig> node : nodes) {
            for (Map.Entry<String, RedisAutoConfiguration.NodeRedisConfig> entry : node.entrySet()) {
                RedisAutoConfiguration.NodeRedisConfig nodeConfig = entry.getValue();
                sentinels.add(new RedisNode(nodeConfig.getHost(), nodeConfig.getPort()));
                if ("".equals(password)) {
                    password = nodeConfig.getPassword();
                } else {
                    if (!password.equals(nodeConfig.getPassword())) {
                        throw new RuntimeException("哨兵模式中redis的密码不一致");
                    }
                }

            }
        }
        if (Strings.isNotBlank(password)) {
            redisConfiguration.setPassword(password);
        }
        redisConfiguration.setClusterNodes(sentinels);
        return redisConfiguration;
    }


    /**
     *  必须将LettuceConnectionFactory注入到spring容器中,否则session共享会出现问题,他会使用容器中的LettuceConnectionFactory
     */
    @Bean
    public LettuceConnectionFactory createLettuceConnectionFactory(RedisAutoConfiguration redisAutoConfiguration) {

        RedisAutoConfiguration.PoolRedisConfig pool = redisAutoConfiguration.getPool();
        //连接池配置
        GenericObjectPoolConfig genericObjectPoolConfig =
                new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(pool.getMinIdle());
        genericObjectPoolConfig.setMaxTotal(pool.getMaxActive());
        genericObjectPoolConfig.setMaxWaitMillis(pool.getMaxWait());
        genericObjectPoolConfig.setJmxEnabled(false);

        //redis客户端配置
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder
                builder = LettucePoolingClientConfiguration.builder().
                commandTimeout(Duration.ofMillis(pool.getTimeout()));

        builder.shutdownTimeout(Duration.ofMillis(pool.getShutdownTimeout()));
        builder.poolConfig(genericObjectPoolConfig);
        LettuceClientConfiguration lettuceClientConfiguration = builder.build();

        RedisConfiguration redisConfiguration = null;
        if ("single".equals(redisAutoConfiguration.getModel())) {
            redisConfiguration = singleRedisTemplateConfig(redisAutoConfiguration);
        } else if ("sentinel".equals(redisAutoConfiguration.getModel())) {
            redisConfiguration = sentinelRedisTemplateConfig(redisAutoConfiguration);
        } else if ("cluster".equals(redisAutoConfiguration.getModel())) {
            redisConfiguration = clusterRedisTemplateConfig(redisAutoConfiguration);
        } else {
            throw new RuntimeException("不支持的redis模式,目前只支持single,sentinel,cluster");
        }

        //根据配置和客户端配置创建连接
        LettuceConnectionFactory lettuceConnectionFactory = new
                LettuceConnectionFactory(redisConfiguration, lettuceClientConfiguration);
        lettuceConnectionFactory.afterPropertiesSet();//初始化连接
        return lettuceConnectionFactory;
    }


    @Override
    public void afterPropertiesSet() {
        if (!redisAutoConfiguration.isSwitchDb()){
            redisTemplateMap.put(redisAutoConfiguration.getDbIndex(), create(redisAutoConfiguration));
            return;
        }
        Integer dbIndex = redisAutoConfiguration.getDbIndex();//保留原来的数据库
        for (int i = 0; i <= 15; i++) {
            redisAutoConfiguration.setDbIndex(i);
            redisTemplateMap.put(i, create(redisAutoConfiguration));
        }
        redisAutoConfiguration.setDbIndex(dbIndex);
    }

    private   RedisTemplate<String, Object> create(RedisAutoConfiguration redisAutoConfiguration){
        LettuceConnectionFactory lettuceConnectionFactory = createLettuceConnectionFactory(redisAutoConfiguration);

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);

        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        redisTemplate.setValueSerializer(genericToStringSerializer); //设置值序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置键序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
